This page last changed on Aug 16, 2012 by jed.wheeler@involver.com.
In this chapter we'll develop a robust user-generated-content (UGC) application leveraging the flexible contest Feature Block. This feature enables rapid development of brand-able UGC experiences by abstracting away common developer concerns such as file uploads & moderation.
The walkthrough builds on ?Chapter 4 - All about Forms - it assumes you are familiar with the signup_form Feature Block.
After completing this chapter you'll be able to create complete contest experiences in your applications.
Blush Magazine Celeb Photo Contest
Blush Magazine wants their community of celebrity obsessed fans to submit their favorite before-and-after photos. Users should be able to upload photos from their computer as well as browse a gallery of approved photos.
Contest Workflow
Before continuing it's important to familiarize ourselves with the workflow of a contest:
- User selects a call-to-action to enter the contest & is presented with an entry_form
- The entry form asks for registration information – information about themselves they need to provide one-time
- The entry form also accepts submission information – the actual content of their entry (any combination of text, photo, video)
- After submitting the entry form, they see a success message
- By default, the user's contest entry is pending and can be moderated via the Involver Audience Management Platform (AMP)
- Approved entries are shown in the gallery
Let's get started!
- In your SML application, add the following code:
<h2>Blush Magazine Celeb Photo Contest</h2>
<p>Show us your favorite before and after photos of celebs!</p>
{% contest %}
{% endcontest %}
- Click "Save Changes". You should now see a new row in the Features table for a "default" contest.
- Click "Configure" under Actions for the contest Feature Block settings.
- Check the box that says "Automatically Approve Contest Entries" & set "Max Contest Entries Per User" to 999 – for development these settings make testing easier
- Click "Save Settings"
- Click "Return to Facebook Page".
- You should see a message says that the Contest has not been built / is incomplete.
The Contest is incomplete because we have not specified the entry form and corresponding success experience.
Adding an Entry Form
The contest entry and success partials are unique within SML in that it cannot be called directly with a render tag, it must be launched with a user click from the contest_form_link. Because the system maps data outputs for the contest form dynamically when that click happens, attempting to render it directly will break your contest's data export. Let's set up our entry and success partials and the link to call them now.
First let's set up the entry partial.
- Update your SML to now read:
{% partial name: "form" %}
{% contest_form %}
<h2>Celeb Photo Contest Entry Form</h2>
{% unless contest_user_is_registered %}
<p><label>Name:</label> <input type="text" name="registration[name]"></p>
<p><label>Email:</label> <input type="text" name="registration[email]"></p>
{% endunless %}
<p><label>Celeb:</label> <input type="text" name="submission[celeb]"></p>
<p><label>Before Photo:</label> {% image_upload_field name:'submission[before_photo]' %}</p>
<p><label>After Photo:</label> {% image_upload_field name:'submission[after_photo]' %}</p>
<p><input type="submit" value="Submit"></p>
{% endcontest_form %}
{% endpartial %}
The Contest Form
Notice that the contest_form contains the html fields in the contest entry form partial the same way the signup_form wraps them. The difference is that where signup_form is a standalone block that can export data directly, the contest_form is part of the larger contest feature block and is used to generate content items.
Contest Form
- Every contest entry form must be wrapped in a contest_form tag
- Like signup_form, you use HTML form fields to collect information
- You many also use special upload field SML tags which enable rich media uploads of photos and video
Contest Form Field Naming Rules
- Any fields that you wish to collect specific to the user’s registration are defined using an input name in the format registration[field_name]
- Similarly, any fields that belong to the actual entry submission have input names in the format of submission[field_name]
Contest Form Context Variables
You have access to several context variables in the entry form:
- contest_user_is_registered (boolean) - true if the user entering has a one or more contest registrations in the system
- contest_user (contest_user) - Access to the user's personal information. These attributes are imported from the social network the application lives in
- contest.rules_text (string) - Rules text configured in the contest feature's settings
- contest.rules_url (string) - Rules URL configured in the contest feature's settings
The Success Page
Once the user has submitted their data we need to show them a confirmation message to let them know we have received their data.
Success Message Context Variables
You have access to the same context variables above in addition to:
- contest_entry - Access to the entry the user just submitted. Use dot notation to reference registration & submission fields – {{ contest_entry.submission.field_name }}
{% partial name: "confirmation" %}
<h2>Success!</h2>
<h3>Thank you, {{ contest_entry.registration.name }} for submitting your entry.</h3>
<p>Email: {{ contest_entry.registration.email }}</p>
<p>Celebrity name: {{contest_entry.submission.celeb }}</p>
<p>Before Photo: {{ contest_entry.submission.before_photo | img_tag }}</p>
<p>After Photo: {{ contest_entry.submission.after_photo | img_tag }}</p>
{% endpartial %}
The contest_form_link
And finally we need to set up the contest feature block and the contest_form_link that launches our entry and success partials.
<h2>Blush Magazine Celeb Photo Contest</h2>
<h3>Show us your favorite before and after photos of celebs!</h3>
{% contest %}
<p>
{% contest_form_link entry_form_partial: "form" success_partial: "confirmation" %}
Click here to enter
{% endcontest_form_link %}
</p>
{% endcontest %}
- Click "Save Settings" then "Return to Facebook Page"
- Click the link and submit your entry. If all goes well, you should see your success message confirming your entry.
Adding Validations
Now that we have fields to collect, we should add validations to make sure the user is submitting valid data. Note that validation within the ?contest_form sub-block works exactly the same as it does in the ?signup_form block.
- Update your entry form partial to now read:
{% partial name: "form" %}
<script>
var rules = {
'registration[email]': {required: true},
'submission[celeb]': {required: true},
'submission[before_photo]': {required: true},
'submission[after_photo]': {required: true}
};
</script>
{% contest_form rules_var: "rules" %}
<h2>Celeb Photo Contest Entry Form</h2>
{% unless contest_user_is_registered %}
<p><label>Name:</label> <input type="text" name="registration[name]"></p>
<p><label>Email:</label> <input type="text" name="registration[email]"></p>
{% endunless %}
<p><label>Celeb:</label> <input type="text" name="submission[celeb]"></p>
<p><label>Before Photo:</label> {% image_upload_field name:'submission[before_photo]' %}</p>
<p><label>After Photo:</label> {% image_upload_field name:'submission[after_photo]' %}</p>
<p><input type="submit" value="Submit"></p>
{% endcontest_form %}
{% endpartial %}
- Click "Save Settings" then "Return to Facebook Page". You should now be prompted with an error if you try submitting without any data.
If this validation approach seems familiar, it should. Validation with contest forms works exactly the same as it does with signup forms. For more details on validations in contests, see IDN:Contest Validation Rules.
Gallery
Now comes the fun part. Let's display accepted entries in a gallery view.
- Go back to the Settings Page
- Update your application's code to now read:
{% partial name: "form" %}
<script>
var rules = {
'registration[email]': {required: true},
'submission[celeb]': {required: true},
'submission[before_photo]': {required: true},
'submission[after_photo]': {required: true}
};
</script>
{% contest_form rules_var: "rules" %}
<h2>Celeb Photo Contest Entry Form</h2>
{% unless contest_user_is_registered %}
<p><label>Name:</label> <input type="text" name="registration[name]"></p>
<p><label>Email:</label> <input type="text" name="registration[email]"></p>
{% endunless %}
<p><label>Celeb:</label> <input type="text" name="submission[celeb]"></p>
<p><label>Before Photo:</label> {% image_upload_field name:'submission[before_photo]' %}</p>
<p><label>After Photo:</label> {% image_upload_field name:'submission[after_photo]' %}</p>
<p><input type="submit" value="Submit"></p>
{% endcontest_form %}
{% endpartial %}
{% partial name: "confirmation" %}
<h2>Success!</h2>
<h3>Thank you, {{ contest_entry.registration.name }} for submitting your entry.</h3>
<p><label>Email:</label> {{ contest_entry.registration.email }}</p>
<p><label>Before Photo:</label> <img src="{{ contest_entry.submission.before_photo }}" alt=""></p>
<p><label>After Photo:</label> <img src="{{ contest_entry.submission.after_photo }}" alt=""></p>
{% endpartial %}
<h2>Blush Magazine Celeb Photo Contest</h2>
<h3>Show us your favorite before and after photos of celebs!</h3>
{% contest %}
<p>
{% contest_form_link entry_form_partial: "form" success_partial: "confirmation" %}
Click here to enter
{% endcontest_form_link %}
</p>
{% for entry in contest.contest_entries %}
<h4>{{ entry.submission.celeb }}</h4>
<p>Submitted by {{ entry.registration.name }}
<p>
{{ entry.submission.before_photo | resize_to: "200" | img_tag }}
{{ entry.submission.after_photo | resize_to: "200" | img_tag }}</p>
{% endfor %}
{% endcontest %}
- Click "Save Changes" then "Return to Facebook Page". You should see your approved entries under the submission link
Moderation
Moderating your contest entries is accomplished via Involver's Audience Management Platform (AMP).
- Contests are grouped by Outlet in AMP; if you haven't already added your Fan Page as an Outlet, then please do so first on the Social Apps tab.
- By default, contest entries start in a pending state to prevent any unwanted content to be published to your page automatically.
- If you want to retroactively moderate your contest entries, simply check the "Automatically Approve Contest Entries" setting available on the contest feature block settings page.
Contest entries can be one of the following states:
- Pending (grey border) - This is the default state, unless you have the auto approve setting enabled.
- Approved (green border) - When this state is set, then the contest entry will be available in the context variable contest.contest_entries when displaying the entries on the front-end.
- Unapproved (red border) - Will mark an entry to not be available in any context variable to display.
- Winner (green border) - This is a way to classify an entry in a special context variable contest.winning_contest_entries for display on the front-end.
To change the state of the entry, simply click the corresponding icon to toggle the state.
If you would like to update the state for a number of entries, select the entries using the check box and then choose and update action from the "Actions..." select box in the upper right corner.
Custom Ordering
Let's add pagination and update our gallery to enable custom ordering. We've done this several times at this point but more practice never hurt anyone. It's worth noting here that because this is a contest we cannot use the facebook_like tag on our entries because doing so would violate Facebook's Terms of Service for contests. Instead we'll use the content_vote tag to allow users to pick their favorites from among the entries submitted by their peers and allow them to sort entries by date submitted and popularity. Keep in mind that content_vote does not currently support restricting the number of items that a person can vote for but you can restrict how frequently they can vote for any single item by using the cookie_days attribute.
- Update your code to now read:
{% partial name: "form" %}
<script>
var rules = {
'registration[email]': {required: true},
'submission[celeb]': {required: true},
'submission[before_photo]': {required: true},
'submission[after_photo]': {required: true}
};
</script>
{% contest_form rules_var: "rules" %}
<h2>Celeb Photo Contest Entry Form</h2>
{% unless contest_user_is_registered %}
<p><label>Name:</label> <input type="text" name="registration[name]"></p>
<p><label>Email:</label> <input type="text" name="registration[email]"></p>
{% endunless %}
<p><label>Celeb:</label> <input type="text" name="submission[celeb]"></p>
<p><label>Before Photo:</label> {% image_upload_field name:'submission[before_photo]' %}</p>
<p><label>After Photo:</label> {% image_upload_field name:'submission[after_photo]' %}</p>
<p><input type="submit" value="Submit"></p>
{% endcontest_form %}
{% endpartial %}
{% partial name: "confirmation" %}
<h2>Success!</h2>
<h3>Thank you, {{ contest_user.name }} for submitting your entry.</h3>
<p><label>Email:</label> {{ contest_entry.registration.email }}</p>
<p><label>Celeb:</label> {{ contest_entry.registration.email }}</p>
<p><label>Before Photo:</label> <img src="{{ contest_entry.submission.before_photo }}" alt=""></p>
<p><label>After Photo:</label> <img src="{{ contest_entry.submission.after_photo }}" alt=""></p>
{% endpartial %}
{% partial name: "gallery" %}
{% paginate contest.contest_entries per_page: 3 order: order_by %}
{% for entry in contest_entries %}
<h3>{{ entry.submission.celeb }}</h3>
<p>submitted by {{ entry.registration.name }}</p>
<h4>{{ entry.submission.celeb }}</h4>
<p>
{{ entry.submission.before_photo | resize_to: "200" | image_tag }}
{{ entry.submission.after_photo | resize_to: "200" | image_tag }}</p>
{% content_vote entry %}
{% endfor %}
{{ pagination_links }}
{% endpaginate %}
{% endpartial %}
<h2>Blush Magazine Celeb Photo Contest</h2>
<h3>Show us your favorite before and after photos of celebs!</h3>
{% contest %}
<p>
{% contest_form_link entry_form_partial: "form" success_partial: "confirmation" %}
Click here to enter
{% endcontest_form_link %}
</p>
{% ajax_link partial: 'gallery' container: 'gallery_container' contest: contest order_by: "created_at DESC" %}
Show newest first
{% endajax_link %} |
{% ajax_link partial: 'gallery' container: 'gallery_container' contest: contest order_by: "created_at ASC" %}
Show oldest first
{% endajax_link %}
{% ajax_link partial: 'gallery' container: 'gallery_container' contest: contest order_by: "vote_count DESC" %}
Show most popular
{% endajax_link %}
<div id="gallery_container">
{% render partial: "gallery" contest: contest order_by: "created_at DESC" %}
</div>
{% endcontest %}
Congratulations, you now have a fully functional contest!
|